﻿using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace BCCommon
{
    public class RedisService : IRedisService
    {
        private readonly ConnectionMultiplexer connection;
        private readonly ISubscriber subscriber;
        private readonly IDatabase redisDatabase;
        private readonly TimeSpan expiry = TimeSpan.FromMinutes(30);
        private bool EnableRedis { get; set; }
        private string RedisConnectionString { get; set; }
        public bool ServerExists { get; set; }
        private readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
        {
            ContractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy { ProcessDictionaryKeys = false } },
            Converters = new List<JsonConverter> {
                new StringEnumConverter { AllowIntegerValues = true },
                new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeLocal, DateTimeFormat = "yyyy-MM-dd HH:mm:ss" }
            },
            NullValueHandling = NullValueHandling.Ignore
        };

        public RedisService(bool enableRedis,string redisConnextionString, string redisDatabaseNo)
        {
            if (connection == null)
            {
#if DEBUG
                Console.WriteLine("Redis开始连接");
#endif
                EnableRedis = enableRedis;
                RedisConnectionString = redisConnextionString;
                var config = new ConfigurationOptions
                {
                    AbortOnConnectFail = false,
                    AllowAdmin = true,
                    ConnectTimeout = 15000,
                    SyncTimeout = 15000,
                    AsyncTimeout = 15000,
                    Password = RedisConnectionString.Split(',')[1].Replace("password=", ""),
                    EndPoints = { RedisConnectionString.Split(',')[0] }
                };
                connection = ConnectionMultiplexer.Connect(config);
                int databaseNo = 0;
                string redisDatabaseNoStr = redisDatabaseNo;
                if (!string.IsNullOrWhiteSpace(redisDatabaseNoStr))
                {
                    databaseNo = Convert.ToInt32(redisDatabaseNoStr);
                }
                this.redisDatabase = connection.GetDatabase(databaseNo);
                this.subscriber = connection.GetSubscriber();
            }
            this.ServerExists = true;
#if DEBUG
            string host = GetRedisHostingInfo(out int port);
            int clientNumber = this.connection.GetServer(host, port).ClientList().Length;
            Console.WriteLine("Redis连接数：{0}", clientNumber);
#endif
        }

        public T TryGet<T>(string key, Func<T> func, TimeSpan? expiry = default)
            where T : class
        {
            ThreadPool.SetMinThreads(200, 200);
            if (EnableRedis)
            {
                T redisObj = this.Get<T>(key);

                if (redisObj != default(T))
                {
                    return redisObj;
                }
            }
            T sqlDbObj = func();

            if (sqlDbObj != null)
            {
                if (expiry.HasValue)
                {
                    this.AddAsync(key, sqlDbObj, expiry);
                }
                else
                {
                    this.AddAsync(key, sqlDbObj, this.expiry);
                }
            }

            return sqlDbObj;
        }

        public T Get<T>(string key)
            where T : class
        {
            ThreadPool.SetMinThreads(200, 200);
            string redisString = this.redisDatabase.StringGetAsync(key).GetAwaiter().GetResult();

            if (redisString == null)
            {
                return null;
            }

            return JsonConvert.DeserializeObject<T>(redisString, JsonSerializerSettings);
        }

        public bool Add<T>(string key, T obj, TimeSpan? expiry = default)
        {

            string objString = JsonConvert.SerializeObject(obj, JsonSerializerSettings);

            return this.redisDatabase.StringSetAsync(key, objString, expiry).GetAwaiter().GetResult();
        }

        public void AddAsync<T>(string key, T obj, TimeSpan? expiry = default)
        {
            string objString = JsonConvert.SerializeObject(obj, JsonSerializerSettings);

            this.redisDatabase.StringSetAsync(key, objString, expiry);
        }

        public bool Delete(string key)
        {
            return this.redisDatabase.KeyDeleteAsync(key).GetAwaiter().GetResult();
        }

        public void DeleteAsync(string key)
        {
            this.redisDatabase.KeyDeleteAsync(key);
        }

        public bool Exists(string key)
        {
            ThreadPool.SetMinThreads(200, 200);
            return this.redisDatabase.KeyExistsAsync(key).GetAwaiter().GetResult();
        }
        /// <summary>
        /// 向某个列表的右侧追加一条记录，右侧代表列表的最后，Key为某个列表的定义名称
        /// </summary>
        /// <typeparam name="T">添加的对象类型</typeparam>
        /// <param name="key">Key为某个列表的定义名称</param>
        /// <param name="obj">要添加的对象值</param>
        public void ListRightPushAsync<T>(string key, T obj)
        {
            string objString = JsonConvert.SerializeObject(obj, JsonSerializerSettings);
            this.redisDatabase.ListRightPushAsync(key, objString);
        }

        /// <summary>
        /// 获取列表第一个值，Key为某个列表的定义名称
        /// </summary>
        /// <typeparam name="T">返回类型</typeparam>
        /// <param name="key">某个列表的定义名称</param>
        /// <returns></returns>
        public T ListLeftPop<T>(string key)
        {
            string redisString = this.redisDatabase.ListLeftPopAsync(key).GetAwaiter().GetResult();
            return JsonConvert.DeserializeObject<T>(redisString, JsonSerializerSettings);
        }
        /// <summary>
        /// 发布一个订阅
        /// </summary>
        /// <typeparam name="T">发布订阅信息类型</typeparam>
        /// <param name="redisChannel">发布订阅的渠道名称</param>
        /// <param name="obj">发布订阅的信息内容</param>
        public void Publish<T>(RedisChannel redisChannel, T obj)
        {
            string objString = JsonConvert.SerializeObject(obj);
            this.subscriber.PublishAsync(redisChannel, objString);
        }
        /// <summary>
        /// 消费一个订阅信息
        /// </summary>
        /// <typeparam name="T">发布订阅信息类型</typeparam>
        /// <param name="redisChannel">发布订阅的渠道名称</param>
        /// <param name="action">用来消费订阅信息的委托方法</param>
        public void Subscriber<T>(RedisChannel redisChannel, Action<RedisChannel, RedisValue> action)
        {
            this.subscriber.SubscribeAsync(redisChannel, action);
        }
        /// <summary>
        /// 设置Key的过期时间
        /// </summary>
        /// <param name="redisKey">redisKey</param>
        /// <param name="expiry">过期时间的TimeSpan</param>
        public void KeyExpireAsync(RedisKey redisKey, TimeSpan? expiry = default)
        {
            this.redisDatabase.KeyExpireAsync(redisKey, expiry);
        }
        /// <summary>
        /// 移除Key的过期时间
        /// </summary>
        /// <param name="redisKey">redisKey</param>
        public void KeyPersistAsync(RedisKey redisKey)
        {
            this.redisDatabase.KeyPersistAsync(redisKey);
        }
        /// <summary>
        /// 拼接方法参数及方法请求值
        /// </summary>
        /// <param name="format">需要拼接的字符串</param>
        /// <param name="args">需要拼接的方法参数值列表</param>
        /// <returns></returns>
        public string GetKey(string format, params object[] args)
        {
            format += ":";
            StackTrace trace = new StackTrace();
            ParameterInfo[] parameterInfo = trace.GetFrame(1).GetMethod().GetParameters();
            for (int i = 0; i < args.Length; i++)
            {
                if (args[i] != null)
                {
                    format += string.Format("{0}={1}:", parameterInfo[i].Name, args[i]);
                }
            }
            return format.TrimEnd(':');
        }

        #region 获取缓存服务器配置信息
        private string GetRedisHostingInfo(out int port)
        {
            string host = RedisConnectionString.Split(',')[0];
            if (string.IsNullOrWhiteSpace(host))
            {
                throw new ArgumentException("Redis缓存配置文件缺少必要的服务器地址信息。");
            }
            if (!host.Contains(":", StringComparison.CurrentCulture))
            {
                throw new ArgumentException("Redis缓存服务器地址信息缺少端口配置信息。");
            }
            string[] hostPoint = host.Split(":");
            if (string.IsNullOrWhiteSpace(hostPoint[1]))
            {
                throw new ArgumentException("Redis缓存服务器地址信息中端口配置信息不能为空。");
            }
            if (!int.TryParse(hostPoint[1], out port))
            {
                throw new ArgumentException("Redis缓存服务器地址信息中端口配置信息不是正确的端口号。");
            }
            return hostPoint[0];
        }
        #endregion

        #region 清理缓存数据库
        public void FlushAsync(int? databasesNo)
        {
            string host = GetRedisHostingInfo(out int port);
            if (databasesNo.HasValue)
            {
                FlushDatabaseAsync(host, port, databasesNo.Value);
            }
            else
            {
                FlushAllDatabaseAsync(host, port);
            }
        }

        private void FlushDatabaseAsync(string host, int port, int databaseNo)
        {
            connection.GetServer(host, port).FlushDatabaseAsync(databaseNo);
        }

        private void FlushAllDatabaseAsync(string host, int port)
        {
            connection.GetServer(host, port).FlushAllDatabasesAsync();
        }
        #endregion

        #region 返回当前数据库的 key 的数量
        public async Task<long> Size(int databaseNo = 0)
        {
            string host = GetRedisHostingInfo(out int port);
            return await connection.GetServer(host, port).DatabaseSizeAsync(databaseNo);
        }
        #endregion

        public async Task<IGrouping<string, KeyValuePair<string, string>>[]> Info(RedisValue? section)
        {
            string host = GetRedisHostingInfo(out int port);
            if (section.HasValue)
            {
                return await connection.GetServer(host, port).InfoAsync(section.Value);
            }
            else
            {
                return await connection.GetServer(host, port).InfoAsync();
            }
        }

        public async Task<string> InfoRaw(RedisValue? section)
        {
            string host = GetRedisHostingInfo(out int port);
            if (section.HasValue)
            {
                return await connection.GetServer(host, port).InfoRawAsync(section.Value);
            }
            else
            {
                return await connection.GetServer(host, port).InfoRawAsync();
            }
        }

        public void DeleteMultipleKey(string prefixKey, bool lauchScript)
        {
            string host = GetRedisHostingInfo(out int port);
            var pattern = prefixKey + "*";
            if (!lauchScript)
            {
                var keys = connection.GetServer(host, port).Keys(database: this.redisDatabase.Database, pattern: pattern);
                this.redisDatabase.KeyDelete(keys.ToArray());
            }
            else
            {
                var redisResult = this.redisDatabase.ScriptEvaluateAsync(LuaScript.Prepare(
                    //Redis的keys模糊查询：
                    " local res = redis.call('KEYS', @keypattern) " +
                    " return res "), new { @keypattern = pattern }).GetAwaiter().GetResult();

                if (!redisResult.IsNull)
                {
                    this.redisDatabase.KeyDelete((RedisKey[])redisResult);  //删除一组key
                }
            }
        }
        public void Dispose()
        {
            this.connection.Dispose();
#if DEBUG
            if (this.redisDatabase.Multiplexer.IsConnected)
            {
                Console.WriteLine("Redis数据库并未关闭释放");
            }
            else
            {
                Console.WriteLine("Redis数据库已关闭释放");
            }
            if (this.subscriber.Multiplexer.IsConnected)
            {
                Console.WriteLine("Redis订阅对象并未关闭释放");
            }
            else
            {
                Console.WriteLine("Redis订阅对象已关闭释放");
            }
#endif
        }

        public string GetSingleRootKey(string format,string primaryKey, object value)
        {
            string key = string.Format("{0}:{1}={2}",format, primaryKey, value);
            return key;
        }
    }
}
